﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using LibNiconico.NiconicoApi.Internal;
using System.Xml.Linq;
using LibNiconico.NiconicoApi.Extentions;
using LibNiconico.NiconicoApi.Exceptions;
using LibNiconico.NiconicoApi.Videos;
using LibNiconico.NiconicoApi.Utils;
using LibNiconico.NiconicoApi.Videos.Comments.PostKey;
using LibNiconico.NiconicoApi.Videos.Comments.Message;
using LibNiconico.NiconicoApi.Videos.WayBackKey;
using LibNiconico.NiconicoApi.Videos.Constant;
using LibNiconico.NiconicoApi.Internal.Http;
using System.Threading;

namespace LibNiconico.NiconicoApi.Videos.Comments
{
    
    /// <summary>
    /// ニコニコ動画コメントクライアント
    /// </summary>
    internal sealed class NicoVideoCommentClient : NicoXmlCommentClient, INicoVideoCommentClient
    {
        /// <summary>
        /// 接続情報
        /// </summary>
        public NicoVideoCommentConnectionInfo ConnectionInfo { get; private set; }

        private NicoVideoCommentConnectionExtendInfo ConnectExtendInfo;

        private string ticket;
        private int lastRes;

        /// <summary>
        /// 接続済みか判定
        /// </summary>
        public bool Connected { get; private set; }

        /// <summary>
        /// 切断イベントハンドラ
        /// </summary>
        public event NicoVideoCommentClientCloseHandler Closed;


        /// <summary>
        /// 読取例外イベントハンドラ
        /// </summary>
        public event NicoVideoCommentClientReadExceptionHandler ReadException;

        /// <summary>
        /// チャット送信結果受信イベントハンドラ
        /// </summary>
        public event NicoVideoCommentClientChatResultHandler ChatResultReceived;

        /// <summary>
        /// チャットコメント受信イベントハンドラ
        /// </summary>
        public event NicoVideoCommentClientChatHandler ChatReceived;

        /// <summary>
        /// スレッドコメント受信イベントハンドラ
        /// </summary>
        public event NicoVideoCommentClientThreadCommentHandler ThreadReceived;

        /// <summary>
        /// ViewCounterイベントハンドラ
        /// </summary>
        public event NicoVideoCommentClientViewCounterHandler ViewCounterReceived;




        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="auth"></param>
        /// <param name="connectionInfo"></param>
        /// <param name="createInfo"></param>
        /// <param name="synchronizationContext"></param>
        private NicoVideoCommentClient(NiconicoAuth auth, NiconicoApiInfoGetCreateInfo createInfo,NicoVideoCommentConnectionInfo connectionInfo,SynchronizationContext synchronizationContext)
            : base(auth, createInfo, synchronizationContext)
        {
            ConnectionInfo = connectionInfo;
            ConnectExtendInfo = null;
            Connected = false;
            Closed = null;
            
            ticket = "";
            clearEventHandler();
        }

        private void clearEventHandler()
        {
            ReadException = null;
            ChatResultReceived = null;
            ChatReceived = null;
            ThreadReceived = null;
            ViewCounterReceived = null;
        }

        internal static NicoVideoCommentClient Create(NiconicoAuth auth, NiconicoApiInfoGetCreateInfo createInfo, NicoVideoCommentConnectionInfo connectionInfo, SynchronizationContext synchronizationContext)
        {
            return new NicoVideoCommentClient(auth, createInfo, connectionInfo, synchronizationContext);
        }

        /// <summary>
        /// 非同期に接続
        /// </summary>
        /// <returns></returns>
        public async Task ConnectAsync()
        {
            await this.ConnectAsync(new NicoVideoCommentConnectionExtendInfo()).ConfigureAwait(false);
        }

        /// <summary>
        /// 非同期に接続
        /// </summary>
        /// <param name="connectExtendInfo"></param>
        /// <returns></returns>
        public async Task ConnectAsync( NicoVideoCommentConnectionExtendInfo connectExtendInfo)
        {
            if (Connected == true) throw new NicoApiException("すでに接続されています");

            ConnectExtendInfo = connectExtendInfo;

            try
            {

                await getReceiveRequest(new NicoVideoMessageQueryInfo { Ms = ConnectionInfo.Ms, Version = ConnectExtendInfo.Version, Thread = ConnectionInfo.Thread, ResFrom = ConnectExtendInfo.ResFrom }).ConfigureAwait(false);
                
                
            }
            catch (Exception e)
            {
                throw new NicoApiException(e.Message,e);
            }
        }

        /// <summary>
        /// 過去コメント受信要求リクエスト送信
        /// </summary>
        /// <param name="wayBackInfo"></param>
        /// <returns></returns>
        public async Task SendWayBackRequest(NicoVideoGetWayBackQueryInfo wayBackInfo)
        {

            await getReceiveRequest(new NicoVideoMessageQueryInfo { Ms = this.ConnectionInfo.Ms, Thread = this.ConnectionInfo.Thread, Version = "20061206", ResBefore = wayBackInfo.ResBefore, ResFrom = wayBackInfo.ResFrom, WayBackKey = await _getWayBackKey().ConfigureAwait(false) }).ConfigureAwait(false);
            
        }

        private async Task<string> _getWayBackKey()
        {
            using (NiconicoInfoGetApiQueryStringsImpl api = new NiconicoInfoGetApiQueryStringsImpl(NicoVideoConstant.NICOVIDEO_URL,NiconicoHttpClient.CreateNiconicoApi(this.Auth)))
            {
                return NicoVideoGetWayBackKeyResponse.Parse((await api.GetAsync(new NicoVideoGetWayBackKeyQueryInfo(this.ConnectionInfo.Thread)).ConfigureAwait(false)).Content).WayBackKey;
            }
        }

        /// <summary>
        /// 非同期にコメント送信
        /// </summary>
        /// <param name="submitChat"></param>
        /// <returns></returns>
        public async Task SubmitAsync(NicoVideoSubmitChatComment submitChat)
        {

            XElement message = base.CreateSubmitChatComment(submitChat);
            message.SetAttributeValue("ticket",this.ticket);
            message.SetAttributeValue("premium", this.ConnectionInfo.Premium);
            message.SetAttributeValue("user_id", this.ConnectionInfo.UserId);

            using (NiconicoInfoGetApiQueryStringsImpl postKeyApi = new NiconicoInfoGetApiQueryStringsImpl(NicoVideoConstant.NICOVIDEO_URL,NiconicoHttpClient.CreateNiconicoApi(this.Auth)))
            {
                message.SetAttributeValue("postkey",(NicoVideoGetPostkeyResponse.Parse((await postKeyApi.GetAsync(new NicoVideoGetPostKeyQuery(lastRes.ToString(),this.ConnectionInfo.Thread)).ConfigureAwait(false)).Content)).PostKey);
            }


            using (NiconicoInfoGetApiXmlImpl messageApi = new NiconicoInfoGetApiXmlImpl(NicoVideoConstant.NICOVIDEO_URL, NiconicoHttpClient.CreateNiconicoApi(this.Auth)))
            {
                privateReadXmlMessage((await messageApi.PostAsync((new NicoVideoMessageSubmitPostInfo(ConnectionInfo.Ms, message))).ConfigureAwait(false)).Content.Element("packet"));
            }
        }

        /// <summary>
        /// 接続を切断
        /// </summary>
        public void Close()
        {
            OnProtectedClose("切断操作により切断します", null);
            Dispose();
            
        }

        private void privateReadXmlMessage(XElement messages)
        {
            try
            {
                var chatList = new List<NicoVideoChatComment>();
                foreach (var message in messages.Elements())
                {
                    base.ReadXmlMessage(message, (chat) =>
                    {
                        chatList.Add(NicoVideoChatComment.Parse(chat));
                    });
                    if (message.Name == "view_counter") PrivateOnViewCounter(
                        new NicoVideoCommentClientViewCounterEventArgs(
                            this,
                            XElementExtention.GetAttributeAsString(message, "id"),
                            XElementExtention.GetAttributeAsString(message, "video"),
                            XElementExtention.GetAttributeAsInt(message, "mylist"))
                        );
                }

                onReadChat(chatList);
            }
            catch (Exception e)
            {
                OnProtectedReadException(e);
            }
        }

        private void PrivateOnViewCounter(NicoVideoCommentClientViewCounterEventArgs args)
        {
            if (ViewCounterReceived != null) ViewCounterReceived(args);
        }


        private void onReadChat(ICollection<NicoVideoChatComment> chatComments)
        {
            if (this.ChatReceived != null)
            {
                ChatReceived(new NicoVideoCommentClientChatEventArgs(this, chatComments));
            }

        }

        /// <summary>
        /// 切断イベントを発生させる
        /// </summary>
        /// <param name="reason"></param>
        /// <param name="e"></param>
        protected override void OnProtectedClose(string reason, Exception e)
        {
            if (Closed != null) Closed(new NicoVideoCommentClientCloseEventArgs (this,  e,  reason));

        }


        /// <summary>
        /// 読取例外イベントを発生させる
        /// </summary>
        /// <param name="e"></param>
        protected override void OnProtectedReadException(Exception e)
        {
            if (ReadException != null) ReadException(new NicoVideoCommentClientReadExceptionEventArgs(this,  e ));
        }

        /// <summary>
        /// チャット送信結果受信イベントを発生させる
        /// </summary>
        /// <param name="message"></param>
        protected override void OnProtectedReadChatResult(XElement message)
        {
            if(ChatResultReceived != null)ChatResultReceived(new NicoVideoCommentClienChatResultEventArgs(this,NicoVideoChatResultComment.Parse(message)));
        }


        /// <summary>
        /// スレッド受信イベントを発生させる
        /// </summary>
        /// <param name="message"></param>
        protected override void OnProtectedReadThread(XElement message)
        {
            var threadEventArgs = new NicoVideoCommentClientThreadCommentEventArgs ( this, NicoVideoThreadComment.Parse(message)) ;
            this.ticket = threadEventArgs.ThreadComment.Ticket;
            this.lastRes = threadEventArgs.ThreadComment.LastRes;
            if (ThreadReceived != null) ThreadReceived(threadEventArgs);
        }

        /// <summary>
        /// このオブジェクトが保有しているリソースを解放する
        /// </summary>
        public void Dispose()
        {
            clearEventHandler();
            ConnectionInfo = null;
            ConnectExtendInfo = null;
            Connected = false;
            
        }

        private async Task getReceiveRequest(NicoUrlAble queryInfo)
        {
            using (NiconicoInfoGetApiXmlImpl messageApi = new NiconicoInfoGetApiXmlImpl(NicoVideoConstant.NICOVIDEO_URL, NiconicoHttpClient.CreateNiconicoApi(this.Auth)))
            {
                var messages = (await messageApi.GetAsync(queryInfo).ConfigureAwait(false)).Content.Element("packet");

                privateReadXmlMessage(messages);

            }
        }


    }
    
}
